Imports System.Windows.Forms
Imports System.Diagnostics
Imports System.Collections
Imports Microsoft.Win32
Imports System.IO
Imports System.Text
Imports System.Runtime.InteropServices


''' <summary>
''' Interface which should be implemented by owner form
''' to use MRUManager.
''' </summary>
Public Interface IMRUClient
    Sub OpenMRUFile(ByVal fileName As String)
End Interface

' MRI list manager.

Public Class ClassMruManager

    Private ownerForm As Form
    ' owner form
    Private menuItemMRU As ToolStripMenuItem
    ' Recent Files menu item
    Private menuItemParent As ToolStripMenuItem
    ' Recent Files menu item parent
    Private registryPath As String
    ' Registry path to keep MRU list
    Private maxNumberOfFiles As Integer = 10
    ' maximum number of files in MRU list
    Private maxDisplayLength As Integer = 40
    ' maximum length of file name for display
    Private currentDirectory As String
    ' current directory
    Private mruList As ArrayList
    ' MRU list (file names)
    Private Const regEntryName As String = "file"
    ' entry name to keep MRU (file0, file1...)


    ''' <summary>
    ''' Initialization. Call this function in form Load handler.
    ''' </summary>
    ''' <param name="owner">Owner form</param>
    ''' <param name="mruItem">Recent Files menu item</param>
    ''' <param name="regPath">Registry Path to keep MRU list</param>
    Public Sub Initialize(ByVal owner As Form, ByVal mruItem As ToolStripMenuItem, _
            ByVal regPath As String)

        ownerForm = owner

        ' check if owner form implements IMRUClient interface
        If Not (TypeOf owner Is IMRUClient) Then
            Throw New Exception("MRUManager: Owner form doesn't implement IMRUClient interface")
        End If

        ' keep reference to MRU menu item
        menuItemMRU = mruItem

        ' keep reference to MRU menu item parent
        Try
            menuItemParent = DirectCast(menuItemMRU.OwnerItem, ToolStripMenuItem)
        Catch
        End Try

        If menuItemParent Is Nothing Then
            Throw New Exception("MRUManager: Cannot find parent of MRU menu item")
        End If

        ' keep Registry path adding MRU key to it
        registryPath = regPath
        If registryPath.EndsWith("\") Then
            registryPath += "MRU"
        Else
            registryPath += "\MRU"
        End If

        ' keep current directory in the time of initialization
        currentDirectory = Directory.GetCurrentDirectory()

        ' subscribe to MRU parent Popup event
        AddHandler menuItemParent.DropDownOpening, New EventHandler(AddressOf Me.OnMRUParentPopup)

        ' subscribe to owner form Closing event
        AddHandler ownerForm.Closing, New System.ComponentModel.CancelEventHandler(AddressOf OnOwnerClosing)

        ' load MRU list from Registry
        LoadMRU()
    End Sub

    Public Sub New()
        mruList = New ArrayList()
    End Sub

    ''' <summary>
    ''' Maximum length of displayed file name in menu (default is 40).
    ''' 
    ''' Set this property to change default value (optional).
    ''' </summary>
    Public Property MaxDisplayNameLength() As Integer
        Get
            Return maxDisplayLength
        End Get
        Set(ByVal value As Integer)
            maxDisplayLength = value
            If maxDisplayLength < 10 Then
                maxDisplayLength = 10
            End If
        End Set
    End Property

    ''' <summary>
    ''' Maximum length of MRU list (default is 10).
    ''' 
    ''' Set this property to change default value (optional).
    ''' </summary>
    Public Property MaxMRULength() As Integer
        Get
            Return maxNumberOfFiles
        End Get
        Set(ByVal value As Integer)
            maxNumberOfFiles = value
            If maxNumberOfFiles < 1 Then
                maxNumberOfFiles = 1
            End If
            If mruList.Count > maxNumberOfFiles Then
                mruList.RemoveRange(maxNumberOfFiles - 1, mruList.Count - maxNumberOfFiles)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Set current directory.
    ''' 
    ''' Default value is program current directory which is set when
    ''' Initialize function is called.
    ''' 
    ''' Set this property to change default value (optional)
    ''' after call to Initialize.
    ''' </summary>
    Public Property CurrentDir() As String
        Get
            Return currentDirectory
        End Get
        Set(ByVal value As String)
            currentDirectory = value
        End Set
    End Property

    ''' <summary>
    ''' Add file name to MRU list.
    ''' Call this function when file is opened successfully.
    ''' If file already exists in the list, it is moved to the first place.
    ''' </summary>
    ''' <param name="file">File Name</param>
    Public Sub Add(ByVal file As String)
        Remove(file)

        ' if array has maximum length, remove last element
        If mruList.Count = maxNumberOfFiles Then
            mruList.RemoveAt(maxNumberOfFiles - 1)
        End If

        ' add new file name to the start of array
        mruList.Insert(0, file)
    End Sub

    ''' <summary>
    ''' Remove file name from MRU list.
    ''' Call this function when File - Open operation failed.
    ''' </summary>
    ''' <param name="file">File Name</param>
    Public Sub Remove(ByVal file As String)
        Dim i As Integer = 0

        Dim myEnumerator As IEnumerator = mruList.GetEnumerator()

        While myEnumerator.MoveNext()
            If DirectCast(myEnumerator.Current, String) = file Then
                mruList.RemoveAt(i)
                Return
            End If

            i += 1
        End While
    End Sub

    ''' <summary>
    ''' Update MRU list when MRU menu item parent is opened
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub OnMRUParentPopup(ByVal sender As Object, ByVal e As EventArgs)
        ' remove all childs
        If menuItemMRU.HasDropDownItems Then
            menuItemMRU.DropDownItems.Clear()
        End If

        ' Disable menu item if MRU list is empty
        If mruList.Count = 0 Then
            menuItemMRU.Enabled = False
            Return
        End If

        ' enable menu item and add child items
        menuItemMRU.Enabled = True

        Dim item As ToolStripMenuItem

        Dim myEnumerator As IEnumerator = mruList.GetEnumerator()

        Dim index As Integer = 1
        While myEnumerator.MoveNext()
            item = New ToolStripMenuItem("&" & index & " " & _
                GetDisplayName(DirectCast(myEnumerator.Current, String)))
            index += 1
            ' subscribe to item's Click event
            AddHandler item.Click, New EventHandler(AddressOf Me.OnMRUClicked)
            menuItemMRU.DropDownItems.Add(item)
        End While
    End Sub

    Private Sub OnMRUClicked(ByVal sender As Object, ByVal e As EventArgs)
        Dim s As String

        'MainForm.quitOrCloseCurrentFile(Me, e)
        Try
            Dim item As ToolStripMenuItem = DirectCast(sender, ToolStripMenuItem)

            If item IsNot Nothing Then
                Dim i As Integer = menuItemMRU.DropDownItems.IndexOf(item)
                s = DirectCast(mruList.Item(i), String)
                ' call owner's OpenMRUFile function
                If s.Length > 0 Then
                    DirectCast(ownerForm, IMRUClient).OpenMRUFile(s)
                End If
            End If
        Catch ex As Exception
            Trace.WriteLine("Exception in OnMRUClicked: " + ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Save MRU list in Registry when owner form is closing
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub OnOwnerClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)
        Dim i As Integer, n As Integer

        Try
            Dim key As RegistryKey = Registry.CurrentUser.CreateSubKey(registryPath)

            If key IsNot Nothing Then
                n = mruList.Count

                For i = 0 To maxNumberOfFiles - 1
                    key.DeleteValue(regEntryName & i.ToString(), False)
                Next

                For i = 0 To n - 1
                    key.SetValue(regEntryName & i.ToString(), mruList(i))
                Next

            End If
        Catch ex As Exception
            Trace.WriteLine("Saving MRU to Registry failed: " & ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Load MRU list from Registry.
    ''' Called from Initialize.
    ''' </summary>
    Private Sub LoadMRU()
        Dim sKey As String, s As String
        Try
            mruList.Clear()
            Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(registryPath)
            If key IsNot Nothing Then
                For i As Integer = 0 To maxNumberOfFiles - 1
                    sKey = regEntryName & i.ToString()
                    s = DirectCast(key.GetValue(sKey, ""), String)
                    If s.Length = 0 Then
                        Exit For
                    End If
                    mruList.Add(s)
                Next
            End If
        Catch ex As Exception
            Trace.WriteLine("Loading MRU from Registry failed: " & ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Get display file name from full name.
    ''' </summary>
    ''' <param name="fullName">Full file name</param>
    ''' <returns>Short display name</returns>
    Private Function GetDisplayName(ByVal fullName As String) As String
        ' if file is in current directory, show only file name
        Dim fileInfo As New FileInfo(fullName)

        If fileInfo.DirectoryName = currentDirectory Then
            Return GetShortDisplayName(fileInfo.Name, maxDisplayLength)
        End If

        Return GetShortDisplayName(fullName, maxDisplayLength)
    End Function

    <DllImport("shlwapi.dll", CharSet:=CharSet.Auto)> _
    Private Shared Function PathCompactPathEx(ByVal pszOut As StringBuilder, ByVal pszPath As String, ByVal cchMax As Integer, ByVal reserved As Integer) As Boolean
    End Function
    ''' <summary>
    ''' Truncate a path to fit within a certain number of characters 
    ''' by replacing path components with ellipses.
    ''' 
    ''' This solution is provided by CodeProject and GotDotNet C# expert
    ''' Richard Deeming.
    ''' 
    ''' </summary>
    ''' <param name="longName">Long file name</param>
    ''' <param name="maxLen">Maximum length</param>
    ''' <returns>Truncated file name</returns>
    Private Function GetShortDisplayName(ByVal longName As String, ByVal maxLen As Integer) As String
        Dim pszOut As New StringBuilder(maxLen + maxLen + 2)
        ' for safety
        'If PathCompactPathEx(pszOut, longName, maxLen, 0) Then
        If PathCompactPathEx(pszOut, longName, maxLen, 0) Then
            Return pszOut.ToString()
        Else
            Return longName
        End If
    End Function

End Class
